/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package generovanieRozpisu;

import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import strukturaRozpis.Rozpis;
import strukturaRozpis.Sluzba;
import strukturaSviatky.Sviatky;
import strukturaSviatky.Sviatok;
import strukturaZamestnanciNepritomnost.Nepritomnost;
import strukturaZamestnanciNepritomnost.Zamestnanci;
import strukturaZamestnanciNepritomnost.Zamestnanec;

/**
 *
 * @author Ondrej
 */
public class ondrejovGenerator {

    private final int POCET_ROZPISOV = 10;
    public final double DLZKA_SLUZBY = 12;
    final String[] VRATNICE = {"M", "F"};
    final String DATE_FORMAT = "dd.MM.yyyy";
    final String HOUR_FORMAT = "HH:mm";  // 24h
    final String[] START_TIMES = {"06:00", "18:00"};
    public static int a = 0, b = 1, c = 0; // rating skupiny = a*vztah + b*sucet_zostavajucih_hodin_dvojice + c*sucet_vahovania_dvojice, cim je vyysi, tym je skupina pravdepodobnejsia, a je dobre mat na 0 kvoli utriedeniu vramci rovnakych zvysnych hodin
    private boolean lenPozitivne = false; // ak true, vytvori len dvojice s nezapornym vztahom (riskatne pouzivat, lebo moze nastat situacia neobsadenia sluzby)
    private Zamestnanci zamestnanci;
    private Sviatky sviatky;
    int mesiac, rok;
    private ArrayList<Dvojica> denneDvojice = new ArrayList<>(), denneDvojice2 = new ArrayList<>(); // zotriedene vsetky mozne denne dvojice podla ratingu od najvyssieho
    private ArrayList<Dvojica> nocneDvojice = new ArrayList<>(), nocneDvojice2 = new ArrayList<>();  // zotriedene vsetky mozne nocne dvojice podla ratingu od najvyssieho
    private ArrayList<Integer> ktoriNemozuMatSluzbu = new ArrayList<>(); // zoznam tych, ktori nemozu mat aktualnu sluzbu
    private ArrayList<Integer> maliNocnu = new ArrayList<>(), maliNocnu2 = new ArrayList<>(); // zoznam tych, co mali poslednu nocnu a nemozu mat dennu
    private ArrayList<Id_zvysneHodiny> zoradeniPodlaChybajucichHodin = new ArrayList<>(), zoradeniPodlaChybajucichHodin2 = new ArrayList<>(); // id zamestnancov s poctom chybajucich hodin zoradene podla toho komu chyba najviac hodin
    private ArrayList<Integer> zamestnanciPodlaHodnotenia = new ArrayList<>(); // id zamestnancov zoradene podla vahovania

    public ondrejovGenerator() {
    }

    public ondrejovGenerator(int mesiac, int rok, ArrayList<Integer> maliPoslednuNocnu) throws SQLException, ParseException {
        this.mesiac = mesiac;
        this.rok = rok;
        this.maliNocnu = maliPoslednuNocnu;
        this.zamestnanci = new Zamestnanci();
        this.sviatky = new Sviatky();
        Rozpis rPom = new Rozpis(mesiac, rok);
        for (Iterator<Entry<Integer, Zamestnanec>> it = zamestnanci.getZamestnanciActive().entrySet().iterator(); it.hasNext();) {
            Map.Entry<Integer, Zamestnanec> elem = it.next();
            zamestnanciPodlaHodnotenia.add(elem.getKey());
            Zamestnanec tmp = elem.getValue();
            zoradeniPodlaChybajucichHodin.add(new Id_zvysneHodiny(elem.getKey(), rPom.getPocetHodin(tmp.getTypZamestnanca())));
            for (Iterator<Entry<Integer, Zamestnanec>> it2 = zamestnanci.getZamestnanciActive().entrySet().iterator(); it2.hasNext();) {
                Map.Entry<Integer, Zamestnanec> elem2 = it2.next();
                Zamestnanec tmp2 = elem2.getValue();

                int vztah = 0;
                if (tmp.getVztahy().get(elem2.getKey()) != null) {
                    vztah = tmp.getVztahy().get(elem2.getKey());
                }

                double sucetHodin = rPom.getPocetHodin(tmp.getTypZamestnanca()) + rPom.getPocetHodin(tmp2.getTypZamestnanca());
                double sucetVahovani = tmp.getVahovanie() + tmp2.getVahovanie();
                double rating = a * vztah + b * sucetHodin + c * sucetVahovani;

                // ak su obaja nocni vytvor dvojicu
                if (lenPozitivne && vztah >= 0) {
                    if (tmp.getTypZamestnanca() == 1 && tmp2.getTypZamestnanca() == 1 && elem.getKey() < elem2.getKey()) {
                        nocneDvojice.add(new Dvojica(elem.getKey(), elem2.getKey(), rating, vztah, 1, sucetVahovani));
                    }

                    // ak su rozni, mozu mat dennu
                    if (elem.getKey() < elem2.getKey()) {
                        denneDvojice.add(new Dvojica(elem.getKey(), elem2.getKey(), rating, vztah, 0, sucetVahovani));
                    }
                } else if (!lenPozitivne) {
                    if (tmp.getTypZamestnanca() == 1 && tmp2.getTypZamestnanca() == 1 && elem.getKey() < elem2.getKey()) {
                        nocneDvojice.add(new Dvojica(elem.getKey(), elem2.getKey(), rating, vztah, 1, sucetVahovani));
                    }

                    // ak su rozni, mozu mat dennu
                    if (elem.getKey() < elem2.getKey()) {
                        denneDvojice.add(new Dvojica(elem.getKey(), elem2.getKey(), rating, vztah, 0, sucetVahovani));
                    }
                }

            }
        }
//        Collections.sort(denneDvojice);
//        Collections.reverse(denneDvojice);
//        Collections.sort(nocneDvojice);
//        Collections.reverse(nocneDvojice);
        denneDvojice = utriedDvojice(denneDvojice);
        nocneDvojice = utriedDvojice(nocneDvojice);

        Collections.sort(zoradeniPodlaChybajucichHodin);
        Collections.reverse(zoradeniPodlaChybajucichHodin);
        Collections.sort(zamestnanciPodlaHodnotenia);
        Collections.reverse(zamestnanciPodlaHodnotenia);

        for (Dvojica dd : denneDvojice) {
            denneDvojice2.add(dd);
        }
        for (Dvojica dd : nocneDvojice) {
            nocneDvojice2.add(dd);
        }
        for (Integer i : maliNocnu) {
            maliNocnu2.add(i);
        }
        for (Id_zvysneHodiny ih : zoradeniPodlaChybajucichHodin) {
            zoradeniPodlaChybajucichHodin2.add(ih);
        }
    }

    /**
     * funkcia utriedi zoznam dvojic najskor podla ratingu, a vramci rovnakeho
     * ratingu, podla vztahu
     *
     * @param list
     */
    private ArrayList<Dvojica> utriedDvojice(ArrayList<Dvojica> list) {
        ArrayList<Dvojica> result = list;
        Collections.shuffle(result);
        Collections.sort(result);
        Collections.reverse(result);
        for (int i = 0; i < result.size(); i++) {
            ArrayList<Dvojica> tmp = new ArrayList<>();
            tmp.add(result.get(i));
            int j = i + 1;
            while (j < result.size() && result.get(j).getRating() == result.get(i).getRating()) {
                tmp.add(result.get(j));
                j++;
            }
            j--;
            Collections.sort(tmp, Dvojica.PODLA_VZTAHU);
            for (int k = 0; k < tmp.size(); k++) {
                result.set(j, tmp.get(k));
                j--;
            }
        }
        return result;
    }

    public void initZoznamy() {
        denneDvojice.clear();
        nocneDvojice.clear();
        maliNocnu.clear();
        zoradeniPodlaChybajucichHodin.clear();

        for (Dvojica dd : denneDvojice2) {
            denneDvojice.add(dd);
        }
        for (Dvojica dd : nocneDvojice2) {
            nocneDvojice.add(dd);
        }
        for (Integer i : maliNocnu2) {
            maliNocnu.add(i);
        }
        for (Id_zvysneHodiny ih : zoradeniPodlaChybajucichHodin2) {
            zoradeniPodlaChybajucichHodin.add(ih);
        }
//        Collections.shuffle(denneDvojice);
//        Collections.sort(denneDvojice);
//        Collections.reverse(denneDvojice);
//
//        Collections.shuffle(nocneDvojice);
//        Collections.sort(nocneDvojice);
//        Collections.reverse(nocneDvojice);

        denneDvojice = utriedDvojice(denneDvojice);
        nocneDvojice = utriedDvojice(nocneDvojice);

        Collections.shuffle(zoradeniPodlaChybajucichHodin);
        Collections.sort(zoradeniPodlaChybajucichHodin);
        Collections.reverse(zoradeniPodlaChybajucichHodin);
    }

    private Id_zvysneHodiny getId_zvysneHodiny(int idZam) {
        for (Id_zvysneHodiny elem : zoradeniPodlaChybajucichHodin) {
            if (idZam == elem.getId()) {
                return elem;
            }
        }
        return null;
    }

    /**
     *
     * @param ktoriPracovali pole indexov zamestnancov, ktori pracovali
     */
    public void updateChybajuceHodinyARating(int[] ktoriPracovali) {
        // zniz hodiny tym, ktori pracovali
        for (Id_zvysneHodiny elem : zoradeniPodlaChybajucichHodin) {
            if (contains(elem.getId(), ktoriPracovali)) {
                elem.updateHodiny(DLZKA_SLUZBY);
            }
        }
        for (Dvojica dd : denneDvojice) {
            if (contains(dd.getZam1(), ktoriPracovali) && contains(dd.getZam2(), ktoriPracovali)) {
                dd.setRating(a * dd.getVztah() + b * (getId_zvysneHodiny(dd.getZam1()).getZvysneHodiny() + getId_zvysneHodiny(dd.getZam2()).getZvysneHodiny()));
            }
        }
        for (Dvojica dd : nocneDvojice) {
            if (contains(dd.getZam1(), ktoriPracovali) && contains(dd.getZam2(), ktoriPracovali)) {
                dd.setRating(a * dd.getVztah() + b * (getId_zvysneHodiny(dd.getZam1()).getZvysneHodiny() + getId_zvysneHodiny(dd.getZam2()).getZvysneHodiny()));
            }
        }

        // utried od najvacsieho
        Collections.shuffle(denneDvojice);
        Collections.sort(denneDvojice);
        Collections.reverse(denneDvojice);

        Collections.shuffle(nocneDvojice);
        Collections.sort(nocneDvojice);
        Collections.reverse(nocneDvojice);

        Collections.shuffle(zoradeniPodlaChybajucichHodin);
        Collections.sort(zoradeniPodlaChybajucichHodin);
        Collections.reverse(zoradeniPodlaChybajucichHodin);
    }

    private void znizPriorituVztahov() {
        if (a > 0) {
            a--;
        }
    }

    /**
     *
     * @param co
     * @param kde
     * @return
     */
    public boolean contains(int co, ArrayList<Integer> kde) {
        for (int i : kde) {
            if (i == co) {
                return true;
            }
        }
        return false;
    }

    /**
     *
     * @param co
     * @param kde
     * @return
     */
    public boolean contains(int co, int[] kde) {
        for (int i : kde) {
            if (i == co) {
                return true;
            }
        }
        return false;
    }

    /**
     * generuje viacero rozpisov a vybere ten najlepsi
     *
     * @return
     * @throws SQLException
     * @throws ParseException
     */
    public Rozpis generuj() throws SQLException, ParseException {
        double min = Double.MAX_VALUE;
        Rozpis result = null;
        Rozpis best = null;
        for (int i = 0; i < POCET_ROZPISOV; i++) {
            initZoznamy();
            result = generujRozpis();
            while (result == null) {
                znizPriorituVztahov();
                initZoznamy();
                result = generujRozpis();
                if (a <= 0 && result == null) {
//                    System.out.println("chyba " + (i + 1));
                    break;
                }
            }
            //rozdiel medzi najvysim poctom chybajucich hodin a najnizsim, cim je tento rozdiel mensi, tym je rozpis spravodlivejsi
            if (result != null && (zoradeniPodlaChybajucichHodin.get(0).getZvysneHodiny() - zoradeniPodlaChybajucichHodin.get(zoradeniPodlaChybajucichHodin.size() - 1).getZvysneHodiny()) < min) {
                best = result;
            }
        }

        return result;
    }

    /**
     *
     * @return @throws SQLException
     * @throws ParseException
     */
    public Rozpis generujRozpis() throws SQLException, ParseException {
        Rozpis result = new Rozpis(mesiac, rok);
        for (Iterator<Entry<Integer, ArrayList<Sluzba>>> it = result.getRozpis().entrySet().iterator(); it.hasNext();) {
            Map.Entry<Integer, ArrayList<Sluzba>> elem = it.next();
            ArrayList<Sluzba> den = new ArrayList<>();
            for (int i = 0; i < 4; i++) {
                den.add(null);
            }
            if (zoradeniPodlaChybajucichHodin.get(0).getZvysneHodiny().equals(new Double(0))) {
                c = 20;
            }

            ktoriNemozuMatSluzbu.clear();

            // pridaj tych co maju volno tento den do zoznamu nepouzitelnych
            for (Iterator<Entry<Integer, ArrayList<Nepritomnost>>> iter = zamestnanci.getNepritomnostZamestnancov().entrySet().iterator(); iter.hasNext();) {
                Map.Entry<Integer, ArrayList<Nepritomnost>> e = iter.next();
                String datum = "";
                if (elem.getKey().toString().length() == 1) {
                    datum = "0" + elem.getKey().toString();
                }
                if (mesiac < 10) {
                    datum += (".0" + mesiac);
                } else {
                    datum += ("." + mesiac);
                }
                datum += ("." + rok);
                for (Nepritomnost n : e.getValue()) {
                    if (n.getDatum().equals(datum)) {
                        ktoriNemozuMatSluzbu.add(e.getKey());
                    }
                }
            }

            ArrayList<Integer> maliNocnuPo = new ArrayList<>();


            // dvaja F nocna
            boolean nasiel = false;
            boolean nezapornyVztah = true;
            while (!nasiel) {
                int threshold = (nezapornyVztah) ? 0 : -3;
                for (Dvojica item : nocneDvojice) {
                    if (!contains(item.getZam1(), ktoriNemozuMatSluzbu) && !contains(item.getZam2(), ktoriNemozuMatSluzbu) && item.getVztah() >= threshold) {
                        nasiel = true;
                        Sluzba Fnocna = new Sluzba(elem.getKey() + "." + mesiac + "." + rok + " " + START_TIMES[1], VRATNICE[1], item.getZam1(), item.getZam2());
                        maliNocnuPo.add(item.getZam1());
                        maliNocnuPo.add(item.getZam2());
                        updateChybajuceHodinyARating(new int[]{item.getZam1(), item.getZam2()});
                        den.set(2, Fnocna);
                        break;
                    }
                }
                if (!nasiel) {
                    nezapornyVztah = false;
                    continue;
                }
                if (!nasiel) {
                    System.out.println("chyba F nocna");
                    return null;
                }
            }


            // dvaja M nocna
            nasiel = false;
            nezapornyVztah = true;
            while (!nasiel) {
                int threshold = (nezapornyVztah) ? 0 : -3;
                for (Dvojica item : nocneDvojice) {
                    if (!contains(item.getZam1(), ktoriNemozuMatSluzbu) && !contains(item.getZam2(), ktoriNemozuMatSluzbu) && !contains(item.getZam1(), maliNocnuPo) && !contains(item.getZam2(), maliNocnuPo) && item.getVztah() >= threshold) {
                        nasiel = true;
                        Sluzba Mnocna = new Sluzba(elem.getKey() + "." + mesiac + "." + rok + " " + START_TIMES[1], VRATNICE[0], item.getZam1(), item.getZam2());
                        maliNocnuPo.add(item.getZam1());
                        maliNocnuPo.add(item.getZam2());
                        updateChybajuceHodinyARating(new int[]{item.getZam1(), item.getZam2()});
                        den.set(3, Mnocna);
                        break;
                    }
                }
                if (!nasiel) {
                    nezapornyVztah = false;
                    continue;
                }
                if (!nasiel) {
                    System.out.println("chyba M nocna");
                    return null;
                }
            }

            for (int i : maliNocnu) {
                ktoriNemozuMatSluzbu.add(i);
            }
            for (int i : maliNocnuPo) {
                ktoriNemozuMatSluzbu.add(i);
            }
            maliNocnu.clear();
            for (int i : maliNocnuPo) {
                maliNocnu.add(i);
            }


            // dvaja M denna
            nasiel = false;
            nezapornyVztah = true;
            while (!nasiel) {
                int threshold = (nezapornyVztah) ? 0 : -3;
                for (Dvojica item : denneDvojice) {
                    if (!contains(item.getZam1(), ktoriNemozuMatSluzbu) && !contains(item.getZam2(), ktoriNemozuMatSluzbu) && item.getVztah() >= threshold) {
                        nasiel = true;
                        Sluzba Mdenna = new Sluzba(elem.getKey() + "." + mesiac + "." + rok + " " + START_TIMES[0], VRATNICE[0], item.getZam1(), item.getZam2());
                        ktoriNemozuMatSluzbu.add(item.getZam1());
                        ktoriNemozuMatSluzbu.add(item.getZam2());
                        updateChybajuceHodinyARating(new int[]{item.getZam1(), item.getZam2()});
                        den.set(1, Mdenna);
                        break;
                    }
                }
                if (!nasiel) {
                    nezapornyVztah = false;
                    continue;
                }
                if (!nasiel) {
                    System.out.println("chyba M denna");
                    return null;
                }
            }


            // jeden F denna (ak je sviatok, alebo vikend -> su dvaja)
            nezapornyVztah = true;
            nasiel = false;
            while (!nasiel) {
                if (jeSviatokAleboVikend(elem.getKey(), mesiac, rok)) {
                    int threshold = (nezapornyVztah) ? 0 : -3;
                    for (Dvojica item : denneDvojice) {
                        if (!contains(item.getZam1(), ktoriNemozuMatSluzbu) && !contains(item.getZam2(), ktoriNemozuMatSluzbu) && item.getVztah() >= threshold) {
                            nasiel = true;
                            Sluzba Fdenna = new Sluzba(elem.getKey() + "." + mesiac + "." + rok + " " + START_TIMES[0], VRATNICE[1], item.getZam1(), item.getZam2());
                            ktoriNemozuMatSluzbu.add(item.getZam1());
                            ktoriNemozuMatSluzbu.add(item.getZam2());
                            updateChybajuceHodinyARating(new int[]{item.getZam1(), item.getZam2()});
                            den.set(0, Fdenna);
                            break;
                        }
                    }
                } else {
                    if (c == 0) { // ak este niekomu chybaju hodiny
                        for (Id_zvysneHodiny item : zoradeniPodlaChybajucichHodin) {
                            if (!contains(item.getId(), ktoriNemozuMatSluzbu)) {
                                nasiel = true;
                                Sluzba Fdenna = new Sluzba(elem.getKey() + "." + mesiac + "." + rok + " " + START_TIMES[0], VRATNICE[1], item.getId(), 0);
                                ktoriNemozuMatSluzbu.add(item.getId());
                                updateChybajuceHodinyARating(new int[]{item.getId()});
                                den.set(0, Fdenna);

                                break;
                            }
                        }
                    } else { // ak uz vsetci odpracovali, kolko maju, vyberajaj z tych z lepsim vahovanim
                        for (Integer item : zamestnanciPodlaHodnotenia) {
                            if (!contains(item, ktoriNemozuMatSluzbu)) {
                                nasiel = true;
                                Sluzba Fdenna = new Sluzba(elem.getKey() + "." + mesiac + "." + rok + " " + START_TIMES[0], VRATNICE[1], item, 0);
                                ktoriNemozuMatSluzbu.add(item);
                                den.set(0, Fdenna);
                                break;
                            }
                        }
                    }
                }
                if (!nasiel) {
                    System.out.println("chyba F denna");
                    return null;
                }
            }


            result.getRozpis().put(elem.getKey(), den);
        }
//        result.commitSluzby();
        return result;
    }

    /**
     *
     * @param zoznam
     * @return true ak je zoznam obsahuje len rozne prvky
     */
    public static boolean allDifferent(ArrayList<Integer> zoznam) {
        for (int i = 0; i < zoznam.size(); i++) {
            for (int j = i + 1; j < zoznam.size(); j++) {
                if (zoznam.get(i).equals(zoznam.get(j))) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * skontroluje rozpis
     *
     * @param r
     * @return
     */
    public static boolean skontrolujRozpis(Rozpis r) {
        for (Iterator<Entry<Integer, ArrayList<Sluzba>>> it = r.getRozpis().entrySet().iterator(); it.hasNext();) {
            Map.Entry<Integer, ArrayList<Sluzba>> elem = it.next();
            ArrayList<Integer> aktualni = new ArrayList<>();
            for (Sluzba item : elem.getValue()) {
                aktualni.add(aktualni.size(), item.getID_zam1());
                aktualni.add(aktualni.size(), item.getID_zam2());
                if (!allDifferent(aktualni)) {
                    System.out.println("Chyba na " + elem.getKey() + " dni");
                    for (Integer i : aktualni) {
                        System.out.print(i + ", ");
                    }
                    return false;
                }
            }
            //vymaz dennych
            aktualni.remove(0);
            aktualni.remove(0);
            aktualni.remove(0);
            aktualni.remove(0);
        }
        return true;
    }

    public static void main(String[] args) throws SQLException, ParseException {
        ArrayList<Integer> nocna = new ArrayList<>();
        nocna.add(44);
        nocna.add(45);
        ondrejovGenerator g = new ondrejovGenerator(1, 2013, nocna);
//        ArrayList<Dvojica> dvojice = new ArrayList<>();
//        dvojice.add(new Dvojica(1, 2, 10, 3, 1, 23));
//        dvojice.add(new Dvojica(3, 4, 10, 2, 1, 23));
//        dvojice.add(new Dvojica(5, 6, 11, 3, 1, 23));
//        dvojice.add(new Dvojica(7, 8, 1, 3, 1, 23));
//        dvojice.add(new Dvojica(9, 10, 11, 1, 1, 23));
//
//        ondrejovGenerator o = new ondrejovGenerator();
//        dvojice = o.utriedDvojice(dvojice);
//
//        for(int i = 0; i < dvojice.size(); i++) {
//            System.out.print("(" + dvojice.get(i).getRating() + ", " + dvojice.get(i).getVztah() + "), ");
//        }
    }

    private boolean jeSviatokAleboVikend(int den, int mesiac, int rok) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(rok, mesiac - 1, den);
        String datum = "";
        if (den < 10) {
            datum += "0";
        }
        datum += String.valueOf(den);
        datum += ".";
        if (mesiac < 10) {
            datum += "0";
        }
        datum += String.valueOf(mesiac);
        datum += ".";
        datum += String.valueOf(rok);
        for (Sviatok item : sviatky.getSviatky()) {
            if (datum.equals(item.getDatum())) {
                return true;
            }
        }
        if (calendar.get(Calendar.DAY_OF_WEEK) == 7 || calendar.get(Calendar.DAY_OF_WEEK) == 1) {
            return true;
        }
        return false;
    }
}
